package pathfinding

import (
	"math"

	"gddgame.cc/galaxy/common/feature/orbit"
)

type HexRule struct {
	Gap  orbit.Point
	Wrap orbit.Point

	instance *HexInstance
}

func NewHexRule() *HexRule {
	instance := NewHexInstance(RowHexMode)
	rule := &HexRule{
		instance: instance,
	}
	return rule
}

func (rule *HexRule) Distance(start []byte, end []byte) int {
	s := DecodeVector2(start)
	e := DecodeVector2(end)
	return int(rule.instance.Distance(s, e))
}

func (rule *HexRule) MinDistance(positions [][]byte, target []byte) int {
	ss := DecodeVector2Slice(positions)
	e := DecodeVector2(target)
	min := float64(-1)
	//fmt.Println(ss, e)
	for _, s := range ss {
		d := rule.instance.Distance(&s, e)
		//fmt.Println(s, e, d, min)
		if min < 0 || d < min {
			min = d
		}
	}
	return int(min)
}

func (rule *HexRule) SmallAngle(start []byte, end []byte) [][]byte {
	var d [][]byte
	s := DecodeVector2(start)
	e := DecodeVector2(end)
	direction := rule.instance.Direction(s, e)
	dirs := rule.instance.GiveUpNeighbours(direction)
	for dir := byte(0); dir < rule.instance.DirectionCount(); dir++ {
		if (1<<uint(dir))&dirs > 0 {
			v := Vector2Pool.Get().(*Vector2)
			rule.instance.GetDirection(v, s.X, s.Y, dir)
			v.X += s.X
			v.Y += s.Y
			d = append(d, v.Encode())
			Vector2Pool.Put(v)
		}
	}
	return d
}

func (rule *HexRule) GetDirection(start []byte, end []byte) []byte {
	s := DecodeVector2(start)
	e := DecodeVector2(end)
	s.X = e.X - s.X
	s.Y = e.Y - s.Y
	return s.Encode()
}

func (rule *HexRule) TriangleDistance(position []byte, distance int) [][]byte {
	p := DecodeVector2(position)
	c := p.EvenQToCube()
	a := [][]byte{}
	for q := 0; q <= distance; q++ {
		for r := 0; r <= distance-q; r++ {
			cc := c.Offset(int16(q), int16(r), int16(-q-r))
			v := (&cc).ToEvenQ()
			a = append(a, (&v).Encode())
		}
	}
	return a
}

func (rule *HexRule) RangeDistance(position []byte, distance int) [][]byte {
	p := DecodeVector2(position)
	c := p.EvenQToCube()
	a := [][]byte{}
	for q := -distance; q <= distance; q++ {
		r1 := math.Max(float64(-distance), float64(-q-distance))
		r2 := math.Min(float64(distance), float64(-q+distance))
		for r := int(r1); r <= int(r2); r++ {
			cc := c.Offset(int16(q), int16(r), int16(-q-r))
			v := (&cc).ToEvenQ()
			a = append(a, (&v).Encode())
		}
	}

	return a
}

func (rule *HexRule) SquareDistance(position []byte, width int, height int) [][]byte {
	p := DecodeVector2(position)
	c := p.EvenQToCube()
	a := [][]byte{}
	for r := 0; r < height; r++ {
		r_offset := int(math.Floor(float64(r / 2))) // or r>>1
		for q := -r_offset; q < width-r_offset; q++ {
			cc := c.Offset(int16(q), int16(r), int16(-q-r))
			v := (&cc).ToEvenQ()
			a = append(a, (&v).Encode())
		}
	}
	return a
}

func (rule *HexRule) RingDistance(position []byte, distance int) [][]byte {
	p := DecodeVector2(position)
	c := p.EvenQToCube()
	a := [][]byte{}
	s := Scale(4, distance)
	c = Offset(&c, &s)
	for i := 0; i < 6; i++ {
		for j := 0; j < distance; j++ {
			v := (&c).ToEvenQ()
			a = append(a, (&v).Encode())
			c = Neighbor(&c, i)
		}
	}
	return a
}

func (rule *HexRule) UpdateLocation(position []byte, sub string) (string, bool) {
	location := orbit.LocationCM.DecodeLocation(position)
	block := orbit.GetBlockWithSub(sub)
	if block.Contain(location) {
		orbit.LocationCM.ReleaseBlock(block)
		orbit.LocationCM.ReleaseLocation(location)
		return sub, false
	}
	sub = orbit.GetSubWithBlock(block)
	orbit.LocationCM.ReleaseBlock(block)
	orbit.LocationCM.ReleaseLocation(location)
	return sub, true
}

func (rule *HexRule) Equal(start []byte, end []byte) bool {
	s := DecodeVector2(start)
	e := DecodeVector2(end)
	return s.Equals(e)
}
